import json
from datetime import datetime
from enum import Enum
from typing import List, Tuple

from bs4 import BeautifulSoup, Tag

from AORemote import AORemote
from TableData import *
import StringUtils


def deduplicate_and_count(objects, attr='no', count_attr='count'):
    result = {}
    for obj in objects:
        # 检查对象类型，使用正确的访问方式
        if isinstance(obj, dict):
            key = obj[attr]  # 字典使用键访问
        else:
            key = getattr(obj, attr)  # 对象使用属性访问

        if key in result:
            # 已存在，增加count
            existing_obj = result[key]
            if isinstance(existing_obj, dict):
                existing_obj[count_attr] = existing_obj.get(count_attr, 0) + 1
            else:
                current_count = getattr(existing_obj, count_attr, 0)
                setattr(existing_obj, count_attr, current_count + 1)
        else:
            # 不存在，添加对象并初始化count
            if isinstance(obj, dict):
                new_obj = obj.copy()
                new_obj[count_attr] = 1
            else:
                new_obj = obj
                if not hasattr(new_obj, count_attr):
                    setattr(new_obj, count_attr, 1)
            result[key] = new_obj
    return list(result.values())


def resolveHeader(aoJson: {}) -> Header:
    aoNum = aoJson['reference']['V_Name']
    aoV = aoJson['reference']['V_versionComment']
    aoVersionIndex = aoJson['reference']['revindex']
    aoName = aoJson['PLM_ExternalID']
    return Header(aoNum, aoV, aoName)


def resolveProcess(aoJson: dict) -> Process:
    rs = Process()
    # 获取工序表内容
    if "Operations" not in aoJson:
        return rs

    operations = aoJson['Operations']
    operationsForRs: typing.List[ProcessRecord] = []
    for index, operation in enumerate(operations):

        # 取出工艺内容 Instructions
        tInstruction = ''
        instructions = operation.get('Instructions', [])
        if instructions and len(instructions) > 0:
            instructionOne = instructions[0]
            txt: str = instructionOne['reference']['V_WIInstruction_Text']
            if txt.startswith('<?'):
                soup = BeautifulSoup(txt, 'html.parser')
                body = soup.body

                content_list = []
                for child in body.children:
                    if isinstance(child, Tag):
                        if child.name == 'p':
                            text = child.get_text(strip=True)
                            if text:  # 过滤空段落
                                content_list.append({
                                    "type": "p",
                                    "content": text
                                })
                        elif child.name == 'table':
                            table_info = {
                                "max_rows": 0,
                                "max_cols": 0,
                                "data": []
                            }
                            rows = child.find_all('tr', recursive=True)
                            for row_idx, tr in enumerate(rows, start=1):
                                cells = tr.find_all(['td', 'th'], recursive=False)
                                if not cells:
                                    continue
                                table_info["max_rows"] = max(table_info["max_rows"], row_idx)
                                table_info["max_cols"] = max(table_info["max_cols"], len(cells))
                                for col_idx, cell in enumerate(cells, start=1):
                                    value = cell.get_text(strip=True)
                                    table_info["data"].append({
                                        "row": row_idx,
                                        "col": col_idx,
                                        "value": value
                                    })
                            content_list.append({
                                "type": "table",
                                "data": table_info
                            })
                tInstruction = json.dumps(content_list)
            else:
                tInstruction = txt

        # 第四列 Tool 数据
        tTools = ''
        resList = []
        resources = operation['resources']
        for resource in resources:
            resList.append(f"{resource['V_Name']} {resource['V_description']}")
        tTools = "\n".join(resList)

        # 数据整合
        tSeq = operation['PLM_ExternalID']
        tProcess = operation['reference']['V_Name']

        isKey = ''
        important = operation['reference'].get('xiziImportantOP', '')
        key = operation['reference'].get('xiziKeyOP', '')
        if important == 'Yes' or key == 'Yes':
            isKey = 'key'

        egI = operation['reference'].get('xiziReferenceImages', '')

        operationsForRs.append(ProcessRecord(tSeq, tProcess, tInstruction, tTools, egI, isKey))
        # 补空行
        operationsForRs.append(ProcessRecord('', '', '', '', '', ''))

    operationsForRs.pop()
    for operation in operationsForRs:
        rs.addProcess(operation)

    return rs


def resolvePart(aoJson: dict) -> TPart:
    rs = TPart()

    if "Parts" not in aoJson:
        return rs

    partsOutput = []
    auxOutput = []

    aoTitle = aoJson['reference']['V_Name']
    parts = aoJson['Parts']
    for part in parts:
        partRef = part['reference']

        partNo: str = partRef['V_Name']

        if partNo.startswith('R_'):
            continue

        partNo = partNo.split('.')[0]

        if part['typeTag'] == 'part':
            typ = part['manu']['reference'].get('xiziDesignType', '')
            partInfo = {'no': partNo, 'name': partRef['V_description'], 'rev': partRef['revision'],
                        'type': typ, 'count': 1, 'next': aoTitle, 'note': ''}
            partsOutput.append(partInfo)
        elif part['typeTag'] == 'manu':
            typ = part['reference'].get('xiziDesignType', '')
            partInfo = {'no': partNo, 'name': partRef['V_description'], 'rev': partRef['revision'],
                        'type': typ, 'count': 0, 'next': aoTitle, 'note': ''}
            auxOutput.append(partInfo)

    partsOutput = deduplicate_and_count(partsOutput, 'no', 'count')
    auxOutput = deduplicate_and_count(auxOutput, 'no', 'count')

    index: int = 1
    for _, part in enumerate(partsOutput):
        rs.addPart(PartRecord(
            f'{index}', part['no'], part['name'], part['rev'], part['type'], f"{part['count']}", part['next'],
            part['note']
        ))
        index = index + 1

    for _, part in enumerate(auxOutput):
        rs.addPart(PartRecord(
            f'{index}', part['no'], part['name'], part['rev'], part['type'], "按需", part['next'],
            part['note']
        ))
        index = index + 1

    return rs


def resolveSketch(aoJson: dict) -> Sketch:
    rs = Sketch()
    if 'Images' not in aoJson:
        return rs

    images = aoJson.get('Images')
    for image in images:
        rs.addImage(image)
    return rs


def resolveResources(aoJson: dict) -> Resources:
    rs = Resources()
    if "Resources" not in aoJson:
        return rs

    resourcesOutput = []
    resources = aoJson['Resources']

    for resource in resources:
        resourcesOutput.append({'no': resource['V_Name'], 'name': resource['V_description'],
                                'spec1': resource.get('xiziSPECS', ''), 'spec2': '', 'spec3': '', 'spec4': ''})

    if 'Operations' in aoJson:
        for operation in aoJson['Operations']:
            if 'resources' in operation:
                for resource in operation['resources']:
                    resourcesOutput.append({'no': resource['V_Name'], 'name': resource['V_description'],
                                            'spec1': resource.get('xiziSPECS', ''), 'spec2': '', 'spec3': '',
                                            'spec4': ''})

    resourcesOutput = deduplicate_and_count(resourcesOutput, 'no', 'count')

    for i, resource in enumerate(resourcesOutput):
        rs.addResourceRecord(ResourceRecord(
            f'{i + 1}',
            resource['no'],
            resource['name'],
            resource['spec1'],
            resource['spec2'],
            resource['spec3'],
            resource['spec4']
        ))

    return rs


# 文档
def resolveDocuments(documentsRetrieved: []) -> TDocument:
    rs = TDocument()

    for d in documentsRetrieved:
        no = d['no']
        name = d['name']

        if no.startswith('CPS'):
            rs.addProcess(DocumentRecord(no, name))
        else:
            rs.addGeneral(DocumentRecord(no, name))

    return rs


def getCAFromWebService(aoId: str, aoVersion: str, remote: AORemote) -> dict:
    ca = remote.getCA(aoId, aoVersion)
    return ca
    # return {}


def normalizeCaRes(caChangesJson: dict):
    rs = {}
    if 'Changes' not in caChangesJson:
        return rs

    rs['CA'] = caChangesJson['CA']
    rs['AO'] = caChangesJson['Changes']['AO']
    rs['AOCA'] = caChangesJson['Changes']['CA']

    if 'signData' not in caChangesJson['Changes']:
        return rs

    signData = caChangesJson.get('Changes').get('signData')
    if signData is None or len(signData) < 1:
        return rs

    filteredCompleted = []
    for item in signData:
        if "attribute[Actual Completion Date]" in item and item["attribute[Actual Completion Date]"]:
            filteredCompleted.append(item)

    for item in filteredCompleted:
        item['date'] = parse_date(item["attribute[Actual Completion Date]"])

    sorted_data_asc = sorted(filteredCompleted, key=lambda x: x["date"])

    titleDict = {item["attribute[Title]"]: item for item in sorted_data_asc}
    for k, v in titleDict.items():
        rs[k] = v

    rs['编制'] = sorted_data_asc[0]

    print(rs)
    return rs


def resolveChanges(aoJson: dict, normalizedCaRes: dict) -> Revision:
    rs = Revision()

    record = RevisionRecord()
    # record.rev = aoJson['reference']['revision'] + aoJson['reference']['V_versionComment']
    record.rev = aoJson['reference']['V_versionComment']

    if 'CA' in normalizedCaRes:
        record.change = normalizedCaRes['CA'].get('description', '')
        record.source = normalizedCaRes['AOCA'].get("xiziChangeBasis", '')

    if '编制' in normalizedCaRes:
        record.revised = normalizedCaRes['编制']['name']

    if '批准' in normalizedCaRes and 'date' in normalizedCaRes['批准']:
        record.date = normalizedCaRes['批准']['date'].strftime("%Y.%m.%d")
        record.approved = normalizedCaRes['批准']['name']

    rs.addRevisionRecord(record)
    return rs


def resolveModel(aoJson: dict, normalizedCaRes: dict) -> Model:
    toCA = lambda name, date: name + '\n' + date.strftime("%Y.%m.%d")
    compiled = ''
    if '编制' in normalizedCaRes:
        compiled = toCA(normalizedCaRes['编制']['name'], normalizedCaRes['编制']['date'])

    checked = ''
    if '校对' in normalizedCaRes:
        checked = toCA(normalizedCaRes['校对']['name'], normalizedCaRes['校对']['date'])

    qa = ''
    if '质保' in normalizedCaRes:
        qa = toCA(normalizedCaRes['质保']['name'], normalizedCaRes['质保']['date'])

    appr = ''
    if '批准' in normalizedCaRes:
        appr = toCA(normalizedCaRes['批准']['name'], normalizedCaRes['批准']['date'])

    model = aoJson['reference']['project']
    proNum = aoJson['PPR']['reference'].get('xiziProjectNo', '')
    partNum = aoJson['reference']['V_Name']

    interchange = aoJson['reference'].get('xiziInterchangeability', '')
    ceDict = {
        'Complete': '互换',
        'NA': 'N/A',
        'Points': '替换-交点互换',
        'Replace': '替换'
    }
    if interchange in ceDict:
        interchange = ceDict[interchange]

    rs = Model()
    rs.model = model
    rs.partNumber = partNum
    rs.proNumber = proNum
    rs.compiled = compiled
    rs.checked = checked
    rs.qa = qa
    rs.appr = appr
    rs.interchange = interchange

    if "Drawings" in aoJson:
        drawings = aoJson['Drawings']
        for i, drawing in enumerate(drawings):
            fromE: str = drawing['reference']['V_fromExternalID']
            if not StringUtils.is_string_non_empty_and_not_only_whitespace(fromE):
                continue

            split = fromE.split(' ')
            rs.addDrawingRecord(DrawingRecord(f'{i + 1}', split[0], 'N/A', split[1], '', '', '', ''))

    # 重要工序
    if "Operations" not in aoJson:
        return rs

    finalKi = _kiCheck(aoJson)

    kiTxt = ''
    if finalKi == _KI.KEY:
        kiTxt = '此AO涉及关键工序'
    elif finalKi == _KI.IMPORTANT:
        kiTxt = '此AO涉及重要工序'
    elif finalKi == _KI.BOTH:
        kiTxt = '此AO涉及关键/重要工序'

    rs.ki = kiTxt

    return rs


class _KI(Enum):
    KEY = 1
    IMPORTANT = 2
    BOTH = 3
    NONE = 0


def _kiCheck(aoJson: {}) -> _KI:
    tagKey = ''
    tagImportant = ''
    operations = aoJson['Operations']
    for operation in operations:
        important = operation['reference'].get('xiziImportantOP', '')
        if important == 'Yes':
            tagImportant = 'Yes'
        key = operation['reference'].get('xiziKeyOP', '')
        if key == 'Yes':
            tagKey = 'Yes'
    if tagKey == 'Yes' and tagImportant == 'Yes':
        return _KI.BOTH
    elif tagKey == 'Yes':
        return _KI.KEY
    elif tagImportant == 'Yes':
        return _KI.IMPORTANT
    else:
        return _KI.NONE


def parse_date(date_str):
    return datetime.strptime(date_str, "%m/%d/%Y %I:%M:%S %p")


def resolveARModel(aoJson: dict) -> ARModel:
    model = aoJson['reference']['project']
    proNum = aoJson['PPR']['reference'].get('xiziProjectName', '')

    rs = ARModel()
    rs.model = model
    rs.programName = proNum

    finalKi = _kiCheck(aoJson)
    if finalKi != _KI.NONE:
        rs.ki = '是'
    else:
        rs.ki = '否'

    return rs


def resolveARSeq(aoJson: dict) -> ARSeq:
    rs = ARSeq()

    if 'Operations' not in aoJson:
        return rs

    operationsForRs: typing.List[ARSeqRecord] = []
    for op in aoJson['Operations']:
        r = ARSeqRecord()
        r.seq = op['PLM_ExternalID']
        r.process = op['reference']['V_Name']

        if op['reference'].get('xiziOperationOP', '') == 'No':
            r.op = 'N/A'

        if op['reference'].get('xiziInspectionOP', '') == 'No':
            r.inspec = 'N/A'

        instructions = op.get('Instructions', [])
        if instructions and len(instructions) > 1:
            instructionOne = instructions[1]
            txt = instructionOne['reference']['V_WIInstruction_Text']
            if txt.startswith('<?'):
                soup = BeautifulSoup(txt, 'html.parser')
                body = soup.body

                content_list = []
                for child in body.children:
                    if isinstance(child, Tag):
                        if child.name == 'p':
                            text = child.get_text(strip=True)
                            if text:  # 过滤空段落
                                content_list.append({
                                    "type": "p",
                                    "content": text
                                })
                        elif child.name == 'table':
                            table_info = {
                                "max_rows": 0,
                                "max_cols": 0,
                                "data": []
                            }
                            rows = child.find_all('tr', recursive=True)
                            for row_idx, tr in enumerate(rows, start=1):
                                cells = tr.find_all(['td', 'th'], recursive=False)
                                if not cells:
                                    continue
                                table_info["max_rows"] = max(table_info["max_rows"], row_idx)
                                table_info["max_cols"] = max(table_info["max_cols"], len(cells))
                                for col_idx, cell in enumerate(cells, start=1):
                                    value = cell.get_text(strip=True)
                                    table_info["data"].append({
                                        "row": row_idx,
                                        "col": col_idx,
                                        "value": value
                                    })
                            content_list.append({
                                "type": "table",
                                "data": table_info
                            })
                tInstruction = json.dumps(content_list)
            else:
                tInstruction = txt

            r.record = tInstruction

        isKey = ''
        important = op['reference'].get('xiziImportantOP', '')
        key = op['reference'].get('xiziKeyOP', '')
        if important == 'Yes' or key == 'Yes':
            isKey = 'key'
        r.key = isKey

        operationsForRs.append(r)
        operationsForRs.append(ARSeqRecord())

    operationsForRs.pop()
    for op in operationsForRs:
        rs.addRecord(op)

    return rs


def resolveARPart(aoJson: dict) -> ARPart:
    rs = ARPart()

    if 'Parts' not in aoJson:
        return rs

    partsOutput = []

    parts = aoJson['Parts']
    for part in parts:
        partRef = part['reference']

        partNo: str = partRef['V_Name']

        if partNo.startswith('R_'):
            continue

        partNo = partNo.split('.')[0]

        typ: str = ''
        count: int = 0
        if part['typeTag'] == 'part':
            typ = part['manu']['reference'].get('xiziDesignType', '')
            count = 1
        elif part['typeTag'] == 'manu':
            typ = part['reference'].get('xiziDesignType', '')
            count = 0

        partInfo = {'no': partNo, 'name': partRef['V_description'], 'rev': partRef['revision'],
                    'type': typ, 'count': count, 'traNo': '', 'note': ''}

        partsOutput.append(partInfo)

    partsOutput = deduplicate_and_count(partsOutput, 'no', 'count')
    for index, part in enumerate(partsOutput):
        if part['count'] > 0:
            countStr = f"{part['count']}"
        else:
            countStr = "按需"

        rs.addRecord(ARPartRecord(
            f'{index + 1}', part['no'], part['name'], part['rev'], part['type'], countStr, part['traNo'],
            part['note']
        ))

    return rs


# 文档
def resolveARRef(aoFileDict: {}, documentsRetrieved: [], normalizedCaRes: dict) -> ARRef:
    rs = ARRef()

    strIss = '参照“附件1：C919项目引用规范文件清单”'
    for index, d in enumerate(documentsRetrieved):
        if d['no'].startswith('XA'):
            continue
        rs.addRecord(ARRefRecord(seq=f"{index + 1}", no=d['no'], name=d['name'], iss=strIss))

    # if '编制' in normalizedCaRes:
    #     rs.sign = normalizedCaRes['编制']['name']
    #     rs.signDate = normalizedCaRes['编制']['date'].strftime("%Y.%m.%d")

    rs.sign = aoFileDict['reference']['owner']
    rs.signDate = datetime.now().strftime("%Y.%m.%d")

    return rs


def getDocumentsFromWebService(aoJson: {}, remote: AORemote) -> {}:
    aoTag = 'DELLmiHeaderOperationReference'
    goTag = 'DELLmiGeneralOperationReference'
    attachment = 'Reference+Document'
    # specification = 'SpecificationDocument'
    reqList = [{
        'type': aoTag,
        'name': aoJson['reference']['PLM_ExternalID'],
        'revision': aoJson['reference']['revision'],
        'relation': attachment
    }]
    if ('Operations' in aoJson) and len(aoJson['Operations']) > 0:
        operations = aoJson['Operations']
        for operation in operations:
            reqList.append({
                'type': goTag,
                'name': operation['reference']['PLM_ExternalID'],
                'revision': operation['reference']['revision'],
                'relation': attachment
            })

    documentsRetrieved = []
    try:
        documentsRespList = remote.getDocuments(reqList)
    except Exception as e:  # 捕获所有异常（慎用！会隐藏潜在问题）
        print(f"获取平台端文档数据失败 {e}")
        raise IOError
    for resp in documentsRespList:  # 注意，一个response可能包含多个document对象
        print(f"response {resp}")
        data_dict = json.loads(resp)
        print(f"Parsing {data_dict}")
        docs = data_dict.get('data', {})
        for d in docs:
            typeName: str = d['type']
            if typeName.startswith("xizi"):
                continue

            relationship: str = d['relationship']
            if relationship == 'SpecificationDocument':
                continue
            documentsRetrieved.append({
                'no': d['name'],
                'name': d['attribute[Title]']
            })
    documentsRetrieved = deduplicate_and_count(documentsRetrieved, 'no', 'count')
    return documentsRetrieved

    # """模拟从Web服务获取文档数据，返回伪造的测试数据"""
    # 伪造的测试数据
    # mock_documents = [
    #     {
    #         'no': 'DOC-001',
    #         'name': '产品规格说明书',
    #         'count': 1
    #     },
    #     {
    #         'no': 'DOC-002',
    #         'name': '安装指南',
    #         'count': 1
    #     },
    #     {
    #         'no': 'DOC-003',
    #         'name': '安全认证报告',
    #         'count': 1
    #     },
    #     {
    #         'no': 'DOC-001',  # 重复文档，测试去重功能
    #         'name': '产品规格说明书(修订版)',
    #         'count': 1
    #     }
    # ]

    # 模拟去重函数的结果
    # return mock_documents


def resolveAREngChange(aoJson: dict) -> AREngChange:
    rs = AREngChange()

    if "Parts" not in aoJson:
        return rs

    parts = aoJson['Parts']
    partsOutput = []

    for part in parts:
        partRef = part['reference']

        partInfo = {'no': partRef['V_Name'], 'name': partRef['V_description'], 'rev': partRef['revision'],
                    'type': '', 'count': 1, 'traNo': '', 'note': ''}

        partsOutput.append(partInfo)

    partsOutput = deduplicate_and_count(partsOutput, 'no', 'count')
    for index, part in enumerate(partsOutput):
        rs.addRecord(
            AREngChangeRecord(f'{index + 1}', part['no'], part['rev'], part['name'], f'{part["count"]}', '', '', ''))

    return rs


from typing import List, Tuple
from bs4 import BeautifulSoup
import math


def parse_one_eff(eff_xml: str) -> List[Tuple[int, int]]:
    """
    解析一个 Eff XML，返回这个 Eff 表达的所有区间的并集（还没合并重叠，只是“列出来”）
    约定：无上界的 Interval(From=15) 记为 (15, +inf)
    """
    soup = BeautifulSoup(eff_xml, "xml")

    series = soup.find("Series")
    if series is None:
        return []

    intervals: List[Tuple[int, int]] = []

    # 1) SingleValue
    for sv in series.find_all("SingleValue"):
        try:
            v = int(sv["Value"])
            intervals.append((v, v))
        except (KeyError, ValueError):
            pass

    # 2) Interval
    for iv in series.find_all("Interval"):
        f_tag = iv.find("From")
        t_tag = iv.find("To")
        if f_tag is None:
            continue

        try:
            from_val = int(f_tag["Value"])
        except (KeyError, ValueError):
            continue

        if t_tag is not None and t_tag.has_attr("Value"):
            try:
                to_val = int(t_tag["Value"])
            except (KeyError, ValueError):
                continue
        else:
            # 无穷大
            to_val = math.inf

        intervals.append((from_val, to_val))

    return intervals


def union_intervals(intervals: List[Tuple[int, int]]) -> List[Tuple[int, int]]:
    """
    把同一个 Eff 里的多个区间做并集，返回一组互不重叠、按起点排序的区间
    """
    if not intervals:
        return []

    # 按起点排序
    intervals = sorted(intervals, key=lambda x: x[0])

    merged: List[Tuple[int, int]] = []
    cur_start, cur_end = intervals[0]

    for s, e in intervals[1:]:
        if s <= cur_end:  # 有重叠或相接
            cur_end = max(cur_end, e)
        else:
            merged.append((cur_start, cur_end))
            cur_start, cur_end = s, e

    merged.append((cur_start, cur_end))
    return merged


def intersect_two_interval_sets(a: List[Tuple[int, int]],
                                b: List[Tuple[int, int]]) -> List[Tuple[int, int]]:
    """
    两个“已排序且不重叠”的区间集合做交集
    """
    i, j = 0, 0
    res: List[Tuple[int, int]] = []
    while i < len(a) and j < len(b):
        a_start, a_end = a[i]
        b_start, b_end = b[j]

        # 交集区间
        start = max(a_start, b_start)
        end = min(a_end, b_end)

        if start <= end:
            res.append((start, end))

        # 谁先结束就向前
        if a_end < b_end:
            i += 1
        else:
            j += 1

    return res


def resolveEffectivity(aoJson: dict) -> str:
    """
    1. 每个 Drawings[i].Eff 里可能有多个表达式（你说实际就一个，我也写成支持多个）
    2. 对 Eff 内部做并集
    3. 多个 Eff 之间做交集
    4. 返回最终的区间字符串，如 "5,7-10,15-∞"
    """
    if "Drawings" not in aoJson:
        return ''

    drawings = aoJson["Drawings"]

    eff_sets: List[List[Tuple[int, int]]] = []

    for d in drawings:
        effArr: List[str] = d.get("Eff", [])
        if not effArr:
            # 这个图没有有效性，就当它“没有限制”？还是当做“空集”？
            # 通常应当当做“空集”，否则会把交集吃掉
            continue

        # 一个 Eff 里可能只有一个 xml
        all_intervals_one_eff: List[Tuple[int, int]] = []
        for eff_xml in effArr:
            intervals = parse_one_eff(eff_xml)
            all_intervals_one_eff.extend(intervals)

        # 并集化
        merged = union_intervals(all_intervals_one_eff)
        if merged:
            eff_sets.append(merged)

    if not eff_sets:
        return ''

    # 多个 Eff 之间做交集
    common = eff_sets[0]
    for s in eff_sets[1:]:
        common = intersect_two_interval_sets(common, s)
        if not common:
            # 没交集了
            raise ValueError("Error: 架次有效性错误（不同图纸之间无公共有效性）")

    # 格式化输出
    parts = []
    for start, end in common:
        if start == end:
            parts.append(fmt(start))
        else:
            parts.append(f"{fmt(start)}-{fmt(end)}")

    return ",".join(parts)


def fmt(num):
    """补足5位，∞ 转换成 09999"""
    if num is math.inf:
        return "09999"
    return f"{int(num):05d}"


class GroupData:
    def __init__(self):
        self.header: Header
        self.model: Model
        self.part: TPart
        self.process: Process
        self.sketch: Sketch
        self.resources: Resources
        self.document: TDocument
        self.changes: Revision
        self.arModel: ARModel
        self.arSeq: ARSeq
        self.arPart: ARPart
        self.arRef: ARRef
        self.arEngChange: AREngChange
        self.effectivity: str

    __slots__ = ['header', 'model', 'part', 'process', 'sketch', 'resources', 'document', 'changes', 'arModel', 'arSeq',
                 'arPart', 'arRef', 'arEngChange', 'effectivity']


def load_ini(aoFileDict: {}, remote: AORemote) -> GroupData:
    # 页眉表格
    header = resolveHeader(aoFileDict)

    aoChanges = getCAFromWebService(aoFileDict['reference']['PLM_ExternalID'], aoFileDict['reference']['revision'], remote)

    remoteDict = {
        'normalizedCA': normalizeCaRes(aoChanges),
        'documents': getDocumentsFromWebService(aoFileDict, remote),
    }

    rs = GroupData()
    rs.header = header
    rs.model = resolveModel(aoFileDict, remoteDict['normalizedCA'])
    rs.part = resolvePart(aoFileDict)
    rs.process = resolveProcess(aoFileDict)
    rs.sketch = resolveSketch(aoFileDict)
    rs.resources = resolveResources(aoFileDict)
    rs.document = resolveDocuments(remoteDict['documents'])
    # rs.changes = resolveChanges(aoFileDict, remoteDict['normalizedCA'])
    rs.arModel = resolveARModel(aoFileDict)
    rs.arSeq = resolveARSeq(aoFileDict)
    rs.arPart = resolveARPart(aoFileDict)
    rs.arRef = resolveARRef(aoFileDict, remoteDict['documents'], remoteDict['normalizedCA'])
    rs.arEngChange = resolveAREngChange(aoFileDict)
    rs.effectivity = resolveEffectivity(aoFileDict)

    return rs
